//****************************************************************
// File Name  : i2c_slave.v
// Author     : xianyong.chen
// Date       : 
// Description: 
//   This module is used to emulate EEPROM 24C02.
// Features   : 
//   (1) Device address is configurable, default is 7'b1010_000
//   (2) Support Byte-Write & Page-Write
//   (3) Support Current-Address-Read, Random-Read and Sequential-Read
//   (4) scl frequency can be from 1KHz to 1MHz
//****************************************************************

module i2c_slave
        #(
        parameter       DEV_ADDR        = 7'b1010_000
        )
        (
        input   wire            sclk,
        input   wire            rst_n,

        // i2c signal           
        input   wire            scl,
        inout   tri             sda,

        output  reg             wr_en,
        output  reg     [7:0]   wr_data,
        output  wire    [7:0]   addr,
        input   wire    [7:0]   rd_data
        );
//****************************************************************
reg     [3:0]   state;
localparam      IDLE                    = 'd0;
localparam      WAIT_START              = 'd1;
localparam      RX_DEV_ADDR             = 'd2;
localparam      RX_CMD                  = 'd3;
localparam      TX_DEV_ADDR_ACK         = 'd4;
localparam      RX_DATA_ADDR            = 'd5;
localparam      TX_DATA_ADDR_ACK        = 'd6;
localparam      RX_DATA                 = 'd7;
localparam      TX_DATA_ACK             = 'd8;
localparam      TX_DATA                 = 'd9;
localparam      RX_DATA_ACK             = 'd10;
localparam      WAIT_STOP               = 'd11;
//****************************************************************
localparam      CMD_WR  = 0;
localparam      CMD_RD  = 1;

localparam      ACK_YES = 0;
localparam      ACK_NO  = 1;

reg     [2:0]   scl_temp;
reg     [2:0]   sda_in_temp;
reg             start;
reg             stop;
reg             scl_posedge;
reg             scl_negedge;

reg     [3:0]   bit_cnt;
reg     [6:0]   dev_addr;
reg             cmd;
reg             ack;
reg     [7:0]   data_addr;

reg     [7:0]   wr_addr;
reg     [7:0]   rd_addr;

wire            sda_in;
reg             sda_out_en;
reg             sda_out;

reg     [14:0]  time_out_cnt;
reg             time_out;
localparam      MAX_TIME_OUT_CNT        = 25000;

//****************************************************************
always@(posedge sclk or negedge rst_n)
        if(!rst_n)
        begin
                scl_temp    <= 3'b111;
                sda_in_temp <= 3'b111;
        end
        else
        begin
                scl_temp    <= {scl_temp[1:0],scl};
                sda_in_temp <= {sda_in_temp[1:0],sda_in};
        end

//start
always@(posedge sclk or negedge rst_n)
        if(!rst_n)
                start <= 'b0;
        else if(scl_temp[1]==1 && sda_in_temp[2:1]==2'b10)
                start <= 1'b1;
        else
                start <= 'b0;

//stop
always@(posedge sclk or negedge rst_n)
        if(!rst_n)
                stop <= 'b0;
        else if(scl_temp[1]==1 && sda_in_temp[2:1]==2'b01)
                stop <= 1'b1;
        else
                stop <= 'b0;

//scl_posedge
always@(posedge sclk or negedge rst_n)
        if(!rst_n)
                scl_posedge <= 'b0;
        else if(scl_temp[2:1]==2'b01)
                scl_posedge <= 1'b1;
        else
                scl_posedge <= 'b0;

//scl_negedge
always@(posedge sclk or negedge rst_n)
        if(!rst_n)
                scl_negedge <= 'b0;
        else if(scl_temp[2:1]==2'b10)
                scl_negedge <= 1'b1;
        else
                scl_negedge <= 'b0;
//****************************************************************
//state[3:0]
always@(posedge sclk or negedge rst_n)
        if(!rst_n)
                state <= IDLE;
        else if(time_out==1)
                state <= IDLE;
        else
        case(state)
                IDLE:
                        state <= WAIT_START;

                WAIT_START:
                        if(start==1)
                                state <= RX_DEV_ADDR;
                //-----------------------------------
                RX_DEV_ADDR:
                        if(scl_negedge==1 && bit_cnt==7)
                                state <= RX_CMD;

                RX_CMD:
                        if(scl_negedge==1)
                                state <= TX_DEV_ADDR_ACK;

                TX_DEV_ADDR_ACK:
                        if(scl_negedge==1)
                        begin
                                if(dev_addr!=DEV_ADDR)
                                        state <= IDLE;
                                else if(cmd==CMD_WR)
                                        state <= RX_DATA_ADDR;
                                else if(cmd==CMD_RD)
                                        state <= TX_DATA;
                        end
                //-----------------------------------
                RX_DATA_ADDR:
                        if(scl_negedge==1 && bit_cnt==7)
                                state <= TX_DATA_ADDR_ACK;

                TX_DATA_ADDR_ACK:
                        if(scl_negedge==1)
                                state <= RX_DATA;
                //-----------------------------------
                RX_DATA:
                        if(start==1)
                                state <= RX_DEV_ADDR;
                        else if(stop==1)
                                state <= IDLE;
                        else if(scl_negedge==1 && bit_cnt==7)
                                state <= TX_DATA_ACK;
                TX_DATA_ACK:
                        if(scl_negedge==1)
                                state <= RX_DATA;
                //-----------------------------------
                TX_DATA:
                        if(scl_negedge==1 && bit_cnt==7)
                                state <= RX_DATA_ACK;

                RX_DATA_ACK:
                        if(scl_negedge==1)
                        begin
                                if(ack==ACK_NO)
                                        state <= WAIT_STOP;
                                else if(ack==ACK_YES)
                                        state <= TX_DATA;
                        end

                WAIT_STOP:
                        if(stop==1)
                                state <= IDLE;
                //-----------------------------------
                default:        state <= IDLE;
        endcase
//****************************************************************
//time_out_cnt[14:0]
always@(posedge sclk or negedge rst_n)
        if(!rst_n)
                time_out_cnt <= 'b0;
        else if(state!=WAIT_START)
        begin
                if(scl_posedge==1 || scl_negedge==1)
                        time_out_cnt <= 'b0;
                else
                        time_out_cnt <= time_out_cnt + 1'b1;
        end
        else
                time_out_cnt <= 'b0;

//time_out
always@(posedge sclk or negedge rst_n)
        if(!rst_n)
                time_out <= 'b0;
        else if(time_out_cnt==MAX_TIME_OUT_CNT)
                time_out <= 1'b1;
        else
                time_out <= 'b0;
//****************************************************************
//bit_cnt[3:0]
always@(posedge sclk or negedge rst_n)
        if(!rst_n)
                bit_cnt <= 'b0;
        else if(state==RX_DEV_ADDR || state==RX_DATA_ADDR
             || state==RX_DATA     || state==TX_DATA)
        begin
                if(scl_negedge==1)
                        bit_cnt <= bit_cnt + 1'b1;
        end
        else
                bit_cnt <= 'b0;

//dev_addr[6:0]
always@(posedge sclk or negedge rst_n)
        if(!rst_n)
                dev_addr <= 'b0;
        else if(state==RX_DEV_ADDR && scl_posedge==1)
                dev_addr <= {dev_addr[5:0],sda_in};

//cmd
always@(posedge sclk or negedge rst_n)
        if(!rst_n)
                cmd <= CMD_RD;
        else if(state==RX_CMD && scl_posedge==1)
                cmd <= sda_in;

//ack
always@(posedge sclk or negedge rst_n)
        if(!rst_n)
                ack <= ACK_NO;
        else if(state==RX_DATA_ACK && scl_posedge==1)
                ack <= sda_in;

//data_addr[7:0]
always@(posedge sclk or negedge rst_n)
        if(!rst_n)
                data_addr <= 'b0;
        else if(state==RX_DATA_ADDR && scl_posedge==1)
                data_addr <= {data_addr[6:0],sda_in};
//****************************************************************
//sda_in
assign sda_in = sda;

//sda
assign sda = (sda_out_en==1) ? sda_out : 1'bz;

//sda_out_en
always@(posedge sclk or negedge rst_n)
        if(!rst_n)
                sda_out_en <= 'b0;
        else if(state==TX_DEV_ADDR_ACK || state==TX_DATA_ADDR_ACK
             || state==TX_DATA_ACK     || state==TX_DATA)
                sda_out_en <= 1'b1;
        else
                sda_out_en <= 'b0;

//sda_out
always@(posedge sclk or negedge rst_n)
        if(!rst_n)
                sda_out <= 'b0;
        else
        case(state)
                TX_DEV_ADDR_ACK:
                                if(dev_addr!=DEV_ADDR)
                                        sda_out <= ACK_NO;
                                else
                                        sda_out <= ACK_YES;
                TX_DATA_ADDR_ACK,
                TX_DATA_ACK:            sda_out <= ACK_YES;

                TX_DATA:                sda_out <= rd_data>>(7-bit_cnt);
        endcase
//****************************************************************
//wr_en
always@(posedge sclk or negedge rst_n)
        if(!rst_n)
                wr_en <= 'b0;
        else if(state==TX_DATA_ACK && scl_posedge==1)
                wr_en <= 1'b1;
        else
                wr_en <= 'b0;

//wr_data[7:0]
always@(posedge sclk or negedge rst_n)
        if(!rst_n)
                wr_data <= 'b0;
        else if(state==RX_DATA && scl_posedge==1)
                wr_data <= {wr_data[6:0],sda_in};

//wr_addr[7:0]
always@(posedge sclk or negedge rst_n)
        if(!rst_n)
                wr_addr <= 'b0;
        else if(state==TX_DATA_ADDR_ACK && scl_posedge==1)
                wr_addr <= data_addr;
        else if(wr_en==1)
                wr_addr <= wr_addr + 1'b1;

//rd_addr[7:0]
always@(posedge sclk or negedge rst_n)
        if(!rst_n)
                rd_addr <= 'b0;
        else if(state==RX_DATA && start==1)
                rd_addr <= data_addr;
        else if(state==RX_DATA_ACK && scl_posedge==1)
                rd_addr <= rd_addr + 1'b1;

//addr[7:0]
assign addr = (wr_en==1) ? wr_addr : rd_addr;

endmodule
